import dateutil.parser
import json

import coin.exchange.base.kr_rest.private_client_base as privcb
import coin.exchange.upbit_v1.kr_rest_reveng.native_private_client as nprivc

from coin.exchange.upbit_v1.kr_rest.constants import coin_list
from coin.exchange.upbit_v1.kr_rest.currency import UpbitCurrency
from coin.exchange.upbit_v1.kr_rest.product import (UpbitProduct)
from coin.proto.coin_query_pb2 import AccountBalance, CurrencyBalance

import coin.proto.coin_order_enums_pb2 as coin_order
import coin.proto.coin_query_pb2 as coin_query
from coin.proto.coin_query_pb2 import (
    AccountBalance,
    CurrencyBalance,
    ProductFillElement,
    ProductFillBundle,
    ProductOrderElement,
    ProductOrderBundle,
)


class UpbitPrivateParser(object):
  currencies = coin_list + ['KRW']

  @staticmethod
  def parse_balance(update_msg):
    assert "error" not in update_msg, update_msg

    account_balance = AccountBalance()
    account_balance.exchange = 'Upbit'

    for update_elem in update_msg:
      if update_elem == "error":
        # If not using json.dumps, will raise UnicodeError.
        raise ValueError(json.dumps(update_msg))
      currency = UpbitCurrency.FromStrNativeCurrency(update_elem["currency"])
      currency_balance = CurrencyBalance()
      currency_balance.currency = currency.currency
      currency_balance.currency_native = currency.native_currency
      currency_balance.available = float(update_elem['balance'])
      currency_balance.hold = float(update_elem['locked'])
      currency_balance.total = currency_balance.available + currency_balance.hold
      account_balance.each_balance.extend([currency_balance])
    return account_balance

  @staticmethod
  def parse_fill(update_msg, product):
    # {
    #    'id':508390240,
    #    'uuid':'c02c4d67-8a01-42bd-add6-55c9a7ce4cc8',
    #    'currency':None,
    #    'base_unit':'QTUM',
    #    'quote_unit':'KRW',
    #    'market':'KRW-QTUM',
    #    'type':'trade',
    #    'trade_type':'bid',
    #    'volume':'17.69268209',
    #    'price':'35100',
    #    'amount':'621014',
    #    'fee':'310.5',
    #    'settlement_amount':'621324',
    #    'krw_price':'1',
    #    'krw_amount':'621014',
    #    'created_at':'2018-02-14T12:33:19+09:00'
    # }
    assert "error" not in update_msg, update_msg

    currency_fill_bundle = ProductFillBundle()
    currency_fill_bundle.exchange = 'Upbit'
    currency_fill_bundle.symbol = product.symbol
    for update_elem in update_msg:
      if update_elem["type"] != "trade":
        continue
      if update_elem["market"] != product.native_symbol:
        continue
      currency_fill = ProductFillElement()
      currency_fill.symbol = product.symbol
      currency_fill.price = float(update_elem['price'])
      currency_fill.qty = float(update_elem['volume'])
      currency_fill.fee = float(update_elem['fee'])
      currency_fill.order_id = update_elem['uuid']
      currency_fill.fill_timestamp = int(
          dateutil.parser.parse(update_elem['created_at']).timestamp() * (10**9))
      if update_elem['trade_type'] == 'ask':
        currency_fill.side = coin_order.SELL_ORDER
      elif update_elem['trade_type'] == 'bid':
        currency_fill.side = coin_order.BUY_ORDER
      else:
        continue
      currency_fill_bundle.each_fill.extend([currency_fill])
    return currency_fill_bundle

  @staticmethod
  def parse_orders(update_msg, product):
    assert "error" not in update_msg, update_msg

    currency_order_bundle = ProductOrderBundle()
    currency_order_bundle.exchange = 'Upbit'
    currency_order_bundle.symbol = product.symbol
    for update_elem in update_msg:
      if update_elem["market"] != product.native_symbol:
        continue
      currency_order = ProductOrderElement()
      currency_order.symbol = product.symbol
      currency_order.price = float(update_elem['price'])
      currency_order.qty = float(update_elem['volume'])
      currency_order.order_id = update_elem['uuid']
      currency_order.created_timestamp = int(
          dateutil.parser.parse(update_elem['created_at']).timestamp() * (10**9))
      if update_elem['side'] == 'ask':
        currency_order.side = coin_order.SELL_ORDER
      elif update_elem['side'] == 'bid':
        currency_order.side = coin_order.BUY_ORDER
      else:
        continue
      currency_order_bundle.each_order.extend([currency_order])
    return currency_order_bundle


class UpbitPrivateClient(privcb.PrivateClientBase):
  ProductType = UpbitProduct

  def __init__(self, key_file):
    privcb.PrivateClientBase.__init__(self)
    self.nprivc = nprivc.UpbitNativePrivateClient(key_file=key_file)

  def query_account_balance_impl(self):
    update = self.nprivc.query_GET_investments_assets()
    update.msg = UpbitPrivateParser.parse_balance(update.msg)
    return update

  def query_fills_impl(self, product):
    symbol = product.native_symbol
    update = self.nprivc.query_GET_investments_history(symbol)
    update.msg = UpbitPrivateParser.parse_fill(update.msg, product)
    return update

  def query_list_orders_impl(self, product):
    symbol = product.native_symbol
    update = self.nprivc.query_GET_orders()
    update.msg = UpbitPrivateParser.parse_orders(update.msg, product)
    return update

  def query_submit_impl(self, product, side, price, qty):
    symbol = product.native_symbol
    update = self.nprivc.query_POST_orders(symbol, "limit", side, price, qty)
    # update.msg = UpbitPrivateParser.parse_orders(update.msg)
    return update

  def query_cancel_impl(self, product, order_id):
    symbol = product.native_symbol
    update = self.nprivc.query_DELETE_order(order_id)
    # update.msg = UpbitPrivateParser.parse_orders(update.msg)
    return update

  def query_cancel_product_impl(self, product):
    orders = self.query_list_orders_impl(product)
    for an_order in orders.msg.each_order:
      self.query_cancel_impl(product, an_order.order_id)
